﻿//-----------------------------------------------------------------------
// <copyright file="User.cs" company="Craig Longford (deltawolf7@gmail.com)">
//     Copyright (c) Craig Longford. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------

namespace Delta.Mercury.Business.User
{
    using System;
    using System.Globalization;
    using System.Text.RegularExpressions;
    using Delta.Data.DataExpress;
    using Delta.Mercury.Business.Exceptions;

    /// <summary>
    /// Reperesentation of the user
    /// </summary>
    [Serializable]
    public class User : IdentifierBase, IUser, IDataServicable
    {
        #region Members
        /// <summary>
        /// Password of the user.
        /// </summary>
        private string password;

        /// <summary>
        /// Firstname of the user.
        /// </summary>
        private string firstname;

        /// <summary>
        /// Lastname of the user.
        /// </summary>
        private string lastname;

        /// <summary>
        /// Date and time the account was created.
        /// </summary>
        private DateTime accountCreated;

        /// <summary>
        /// Users email address.
        /// </summary>
        private string emailAddress;
        #endregion

        #region Constructors
        /// <summary>
        /// Initializes a new instance of the User class.
        /// </summary>
        public User() 
            : this(string.Empty, string.Empty, string.Empty, false)
        {
            this.password = string.Empty;
            this.AccountActive = true;
            this.LastAccess = DateTime.Now;
            this.IsAdministrator = false;
            this.accountCreated = DateTime.Now;
        }

        /// <summary>
        /// Initializes a new instance of the User class.
        /// </summary>
        /// <param name="firstname">User firstname</param>
        /// <param name="lastname">User lastname</param>
        /// <param name="emailAddress">Email address</param>
        /// <param name="isAdministrator">Is administrator</param>
        public User(string firstname, string lastname, string emailAddress, bool isAdministrator)
            : base()
        {
            this.firstname = firstname;
            this.lastname = lastname;
            this.emailAddress = emailAddress;
            this.IsAdministrator = isAdministrator;
            this.accountCreated = DateTime.Now;
            this.LastAccess = DateTime.Now;
            this.password = string.Empty;
        }
        #endregion

        #region Properties
        /// <summary>
        /// Gets or sets the user's firstname.
        /// <remarks>
        /// Auto title casing
        /// </remarks>
        /// </summary>
        public virtual string Firstname 
        {
            get
            {
                return this.firstname;
            }

            set
            {
                if (value.Length < 1)
                {
                    throw new InvalidLengthException("User firstname must be a minimum of 1 characters.");
                }

                if (value.Length > 20)
                {
                    throw new InvalidLengthException("User firstname must be a maximum of 20 characters.");
                }

                TextInfo info = CultureInfo.CurrentCulture.TextInfo;
                this.firstname = info.ToTitleCase(value);
            }
        }

        /// <summary>
        /// Gets or sets the user's lastname.
        /// <remarks>
        /// Auto title casing
        /// </remarks>
        /// </summary>
        public virtual string Lastname
        {
            get
            {
                return this.lastname;
            }

            set
            {
                if (value.Length < 1)
                {
                    throw new InvalidLengthException("User lastname must be a minimum of 1 characters.");
                }

                if (value.Length > 20)
                {
                    throw new InvalidLengthException("User lastname must be a maximum of 20 characters.");
                }

                TextInfo info = CultureInfo.CurrentCulture.TextInfo;
                this.lastname = info.ToTitleCase(value);
            }
        }

        /// <summary>
        /// Gets or sets the users email address.
        /// <remarks>
        /// Auto lowercase
        /// </remarks>
        /// </summary>
        public virtual string EmailAddress
        {
            get
            {
                return this.emailAddress;
            }

            set
            {
                if (value.Length < 1)
                {
                    throw new InvalidLengthException("User email address must be a minimum of 1 characters.");
                }

                if (value.Length > 200)
                {
                    throw new InvalidLengthException("User email address must be a maximum of 200 characters.");
                }

                Regex regValidate = new Regex(@"^([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$");
                if (regValidate.IsMatch(value))
                {
                    this.emailAddress = value.ToLower();
                }
                else
                {
                    throw new InvalidEmailAddressException("The user's email address is invalid.");
                }               
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the user's account is active or inactive.
        /// </summary>
        public virtual bool AccountActive { get; set; }

        /// <summary>
        /// Gets the date and time the account was created.
        /// </summary>
        public virtual DateTime Created
        {
            get
            {
                return this.accountCreated;
            }
        }

        /// <summary>
        /// Gets or sets the date and time the account was last accessed.
        /// </summary>
        public virtual DateTime LastAccess { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether the user is an administrator
        /// </summary>
        public virtual bool IsAdministrator { get; set; }

        /// <summary>
        /// Gets the users full name
        /// </summary>
        public string Fullname
        {
            get
            {
                return this.firstname + " " + this.lastname;
            }
        }
        #endregion

        #region Static Load Methods
        /// <summary>
        /// Load user by email address
        /// </summary>
        /// <param name="email">Email address</param>
        /// <param name="showDeleted">Show deleted</param>
        /// <returns>User by email address</returns>
        public static User Load(string email, bool showDeleted)
        {
            Parameters parameters = new Parameters();
            parameters.Add(new Parameter("Email", email));
            parameters.Add(new Parameter("Deleted", showDeleted));
            return DataAccess.GetSingle<User>("LoadUser", System.Data.CommandType.StoredProcedure, parameters);
        }

        /// <summary>
        /// Load user by unique identifier
        /// </summary>
        /// <param name="guid">Unqiue identifier</param>
        /// <param name="showDeleted">Show deleted</param>
        /// <returns>User by unique identifier</returns>
        public static User Load(Guid guid, bool showDeleted)
        {
            Parameters parameters = new Parameters();
            parameters.Add(new Parameter("Guid", guid));
            parameters.Add(new Parameter("Deleted", showDeleted));
            return DataAccess.GetSingle<User>("LoadUser", System.Data.CommandType.StoredProcedure, parameters);
        }

        /// <summary>
        /// Load a user by matching email and password
        /// </summary>
        /// <param name="email">Email address</param>
        /// <param name="password">Unencrypted password</param>
        /// <returns>Valid user it login is correct</returns>
        public static User Load(string email, string password)
        {
            Parameters parameters = new Parameters();
            parameters.Add(new Parameter("Email", email));
            parameters.Add(new Parameter("Password", MercurySystem.EncryptString(password)));
            User user = DataAccess.GetSingle<User>("LoadUserLogin", System.Data.CommandType.StoredProcedure, parameters);
            user.LastAccess = DateTime.Now;
            user.Save();
            return user;
        }
        #endregion

        #region Load Methods
        /// <summary>
        /// Get user's contacts
        /// </summary>
        /// <param name="showDeleted">Show deleted</param>
        /// <returns>Collection of user contacts</returns>
        public virtual Contacts GetContacts(bool showDeleted)
        {
            return Contacts.Load(this, false);
        }

        /// <summary>
        /// Get user's preferences
        /// </summary>
        /// <returns>Collection of preferences</returns>
        public virtual Preferences GetPreferences()
        {
            return Preferences.Load(this);
        }

        /// <summary>
        /// Gets user's addresses
        /// </summary>
        /// <param name="showDeleted">Show deleted</param>
        /// <returns>Collection of addresses</returns>
        public virtual Addresses GetAddresses(bool showDeleted)
        {
            return Addresses.Load(this, showDeleted);
        }
        #endregion

        #region Password Methods
        /// <summary>
        /// Set the persons password.
        /// <remarks>The persons password will only be set if current password is correct.
        /// Useful for allowing the person to change their own password.
        /// </remarks>
        /// </summary>
        /// <param name="currentPassword">String current password to check</param>
        /// <param name="newPassword">String new password</param>
        public virtual void SetPassword(string currentPassword, string newPassword)
        {
            if (MercurySystem.EncryptString(currentPassword) == this.password)
            {
                this.password = MercurySystem.EncryptString(newPassword);
            }
            else
            {
                throw new InvalidPasswordException("Current password doesn't match the password on record. Password was not set.");
            }
        }

        /// <summary>
        /// Force set the persons password without checking the current password.
        /// <remarks>Useful for setting up a new persons password.</remarks>
        /// </summary>
        /// <param name="newPassword">String password to set</param>
        public virtual void SetPassword(string newPassword)
        {
            this.password = MercurySystem.EncryptString(newPassword);
        }

        /// <summary>
        /// Check a password
        /// </summary>
        /// <param name="password">Password to check</param>
        /// <returns>True if correct, else false</returns>
        public virtual bool CheckPassword(string password)
        {
            if (MercurySystem.EncryptString(password) == this.password)
            {
                return true;
            }
            else
            {
                return false;
            }
        }
        #endregion

        #region Save Methods
        /// <summary>
        /// Save user to database
        /// </summary>
        public virtual void Save()
        {
            DataAccess.NonQuery("SaveUser", System.Data.CommandType.StoredProcedure, GetSaveParameters());
        }
        #endregion

        #region Delete Methods
        /// <summary>
        /// Delete user from database
        /// </summary>
        public virtual void Delete()
        {
            Parameters parameters = new Parameters();
            parameters.Add(new Parameter("Guid", this.Guid));
            DataAccess.NonQuery("DeleteUser", System.Data.CommandType.StoredProcedure, parameters);
        }
        #endregion

        #region Fill Method
        /// <summary>
        /// Fill a person object with data from a database
        /// </summary>
        /// <param name="reader">Database reader</param>
        public virtual void Fill(System.Data.IDataReader reader)
        {
            this.firstname = Helper.ToString(reader["Firstname"]);
            this.password = Helper.ToString(reader["Password"]);
            this.lastname = Helper.ToString(reader["Lastname"]);
            this.AccountActive = Helper.ToBool(reader["AccountActive"]);
            this.accountCreated = Helper.ToDateTime(reader["Created"]);
            this.Guid = Helper.ToGuid(reader["Guid"]);
            this.LastAccess = Helper.ToDateTime(reader["LastAccess"]);
            this.IsAdministrator = Helper.ToBool(reader["IsAdministrator"]);
            this.emailAddress = Helper.ToString(reader["EmailAddress"]);
        }
        #endregion

        #region Get Save Paramaters
        /// <summary>
        /// Get save parameters
        /// </summary>
        /// <returns>Parameter collection</returns>
        public Parameters GetSaveParameters()
        {
            Parameters parameters = new Parameters();
            parameters.Add(new Parameter("AccountActive", this.AccountActive));
            parameters.Add(new Parameter("Created", this.Created));
            parameters.Add(new Parameter("Firstname", this.Firstname));
            parameters.Add(new Parameter("Guid", this.Guid));
            parameters.Add(new Parameter("LastAccess", this.LastAccess));
            parameters.Add(new Parameter("Lastname", this.Lastname));
            parameters.Add(new Parameter("IsAdministrator", this.IsAdministrator));
            parameters.Add(new Parameter("Password", this.password));
            parameters.Add(new Parameter("Email", this.emailAddress));
            return parameters;
        }
        #endregion
    }
}
